package game;

import java.io.IOException;
import java.net.Socket;
import java.util.Collections;
import java.util.LinkedHashMap;
import java.util.Map;

/**
 * PlayersManager delegates the interaction between all players who have been
 * connected to the server.
 * <p>
 * PlayersManager is a resource which all players(threads) try to access.
 * 
 * @author Tsvetan Vasilev
 * 
 */
public class PlayersManager {
	// indexer is used for generation of unique playerId-s
	private int indexer = 0;

	// A synchronized Map of ALL players in the game - map the Player's
	// ID with the corresponding object
	private Map<Integer, Player> id2Players = Collections
			.synchronizedMap(new LinkedHashMap<Integer, Player>());

	// A synchronized Map of ONLY THE FREE players in the game - map the
	// Player's ID with the corresponding object
	//
	private Map<Integer, Player> id2FreePlayers = Collections
			.synchronizedMap(new LinkedHashMap<Integer, Player>());

	// --- Methods -------------------------------------------------------------
	/**
	 * Creates a new {@link Player} who corresponds to accepted socket.
	 * <p>
	 * A new {@link Thread} is started which will executes player's actions.
	 * 
	 * @param socket
	 *            accepted socket by the server
	 */
	public synchronized void addPlayer(Socket socket) throws IOException {
		PlayerIOManager playerIOManager = new PlayerIOManager(socket);

		Player player = new Player(indexer++, null, playerIOManager, this);
		addPlayer(player);

		Thread playerThread = new Thread(player);
		playerThread.start();
	}

	public synchronized void deletePlayer(Player player) {
		id2Players.remove(player.getId());
		id2FreePlayers.remove(player.getId());
	}

	/**
	 * 
	 * @return the count of free players
	 */
	public int getFreePlayersCount() {
		return id2FreePlayers.size();
	}

	/**
	 * Creates a new {@link Game} after The {@link Player} has chosen his/her
	 * opponent
	 * 
	 * @param player
	 * @param chosenIndex
	 * @return
	 */
	public synchronized boolean createGame(Player player, int chosenIndex) {
		// map chosenIndex to opponentId
		int opponentId = mapChosenIndex2PlayerId(chosenIndex);

		// check if opponent is free and
		// remove him from freePlayersList if he is there.
		Player opponent = id2FreePlayers.remove(opponentId);
		if (opponent == null) {
			return false;
		}

		// remove current player
		id2FreePlayers.remove(player.getId());

		// initiate new game
		Game game = new Game(player, opponent);
		player.setGame(game);
		opponent.setGame(game);

		// notifyAll to notify opponent for allocated new game
		notifyAll();
		return true;
	}

	/**
	 * Delivers a map between id and <b>free</b> Player's name
	 * 
	 * @return
	 */
	public Map<Integer, String> getId2NamesFreePlayers() {
		Map<Integer, String> id2NamesFreePlayers = new LinkedHashMap<Integer, String>();
		for (Map.Entry<Integer, Player> entry : id2FreePlayers.entrySet()) {
			id2NamesFreePlayers.put(entry.getKey(), entry.getValue().getName());
		}
		return id2NamesFreePlayers;
	}

	/**
	 * Delegate the actions of player who has chosen to wait in the game.
	 * 
	 * @param player
	 * @throws InterruptedException
	 */
	public synchronized void superviseWaitingPlayer(Player player)
			throws InterruptedException {
		// 1.1 add current player to free players
		id2FreePlayers.put(player.getId(), player);

		// 1.2 force currentPlayer to wait
		wait();

		// 1.3 somebody has notified me
		// check if I have been chosen to play game
		while (id2FreePlayers.containsKey(player.getId())) {
			// if I'm not chosen(so I'm free), wait again.
			wait();
		}
	}

	// --- Auxiliary Methods ---------------------------------------------------
	private synchronized void addPlayer(Player player) {
		id2Players.put(player.getId(), player);
	}

	private Player mapChosenIndex2Player(int chosenPlayerIndex) {
		int counter = 1;
		for (Map.Entry<Integer, Player> entry : id2FreePlayers.entrySet()) {
			if (counter == chosenPlayerIndex) {
				return entry.getValue();
			}
			counter++;
		}
		return null;
	}

	private int mapChosenIndex2PlayerId(int chosenIndex) {
		return mapChosenIndex2Player(chosenIndex).getId();
	}

}
